﻿using System;
using System.Activities;
using System.Activities.Hosting;
using System.Activities.Statements.Tracking;
using System.Activities.Tracking;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.SqlClient;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using SLS.ExClassLib.Events;
using SLS.ExClassLib.Message;
using SLS.ExClassLib.MVVM;
using SLS.ExClassLib.WFActivities;
namespace SLS.ExClassLib.MVVM
{
    public partial class WFMachine
    {
        public string HostAddress
        {
            get;
            set;
        }
        public IDataModel ViewModel;
        public Activity WFActivity;
        public string WFMAddress
        {
            get;
            set;
        }

        public WFMachine(string address, IDataModel vwModel, Activity wfActivity)
        {
            this.HostAddress = address;
            this.WFMAddress = address;
            this.WFActivity = wfActivity; 
            this.InputJobBuffer = new Queue<MessageEx>();
            this.OutputJobBuffer = new Queue<MessageEx>();
            this.ViewModel = vwModel;
            this.InitializeCommands();
        }

        #region==================PROPERTIES=================
        string connectionString;
        public string ConnectionString
        {
            get
            {
                return connectionString;
            }

            set
            {
                connectionString = GetConnectionString(value);
            }
        }

        internal static String GetConnectionString(String rawConnectionString)
        {
            SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder
            {
                ConnectionString = rawConnectionString,
                AsynchronousProcessing = true,
                Enlist = false
            };
            return builder.ToString();
        }
        public Queue<MessageEx> MQ = new Queue<MessageEx>();
        public Queue<MessageEx> InputJobBuffer;
        public Queue<MessageEx> OutputJobBuffer;
        #region Constants and Fields

        /// <summary>
        ///   The default timeout.
        /// </summary>
        public readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(30);

        /// <summary>
        ///   The transition enabled.
        /// </summary>
        public readonly Dictionary<MachineTransition, bool> transitionEnabled = new Dictionary<MachineTransition, bool>();

        /// <summary>
        ///   The idle event.
        /// </summary>
        public readonly ManualResetEvent workflowBusy = new ManualResetEvent(false);

        /// <summary>
        ///   The time that the ATM User has to enter a command
        /// </summary>
        public TimeSpan atmTimeout = TimeSpan.FromSeconds(3000);

        /// <summary>
        ///   The camera on.
        /// </summary>
        private bool cameraOn;

        /// <summary>
        ///   The hardware initialized.
        /// </summary>
        public bool hardwareInitialized;

        /// <summary>
        ///   The timeout.
        /// </summary>
        private TimeSpan timeout;

        #endregion

        #region Properties

        /// <summary>
        ///   Gets or sets AtmTimeout.
        /// </summary>
        public TimeSpan AtmTimeout
        {
            get
            {
                return this.atmTimeout;
            }

            set
            {
                this.atmTimeout = value;
            }
        }

        /// <summary>
        ///   Gets WorkflowApplication.
        /// </summary>
        public WorkflowApplication WFHost { get; set; }

        /// <summary>
        ///   Gets a value indicating whether PowerOn.
        /// </summary>
        public bool PowerOn { get; set; }

        /// <summary>
        ///   Gets or sets Timeout.
        /// </summary>
        public TimeSpan Timeout
        {
            get
            {
                if (this.timeout == TimeSpan.Zero)
                {
                    this.timeout = this.DefaultTimeout;
                }

                return this.timeout;
            }

            set
            {
                this.timeout = value;
            }
        }

        /// <summary>
        ///   Gets CurrentState.
        /// </summary>
        public string CurrentState { get; set; }

        #endregion
        /// <summary>
        /// Determines if the state machine is currently in this state
        /// </summary>
        /// <param name="state">
        /// The state.
        /// </param>
        /// <returns>
        /// true if it is in the state, false if not
        /// </returns>
        public bool IsCurrentState(string state)
        {
            return this.CurrentState == state;
        }
        #endregion

        #region=================Public Methods==================

        public static MachineTransition ConvertToAtmTransition(string commandString)
        {
            MachineTransition cmd;

            if (!Enum.TryParse(commandString, out cmd))
            {
                throw new ArgumentException(
                    string.Format("Invalid transition \"{0}\" - must be an MachineTransition value", commandString),
                    "commandString");
            }

            return cmd;
        }
        /// <summary>
        /// Determines if a transition is enabled
        /// </summary>
        /// <param name="transition">
        /// The MachineTransition value.
        /// </param>
        /// <returns>
        /// true if the transition is enabled, false if not
        /// </returns>
        public bool IsTransitionEnabled(MachineTransition transition)
        {
            return this.transitionEnabled[transition];
        }

        /// <summary>
        /// Determines if a transition is enabled
        /// </summary>
        /// <param name="commandString">
        /// The transition string.
        /// </param>
        /// <returns>
        /// true if the transition is enabled, false if not
        /// </returns>
        public bool IsTransitionEnabled(string commandString)
        {
            return this.IsTransitionEnabled(ConvertToAtmTransition(commandString));
        }
        public virtual void InputJobPool(MessageEx Jobmsg)
        {
            try
            {
                this.InputJobBuffer.Enqueue(Jobmsg);
            }
            catch (Exception ex)
            {
                Tracer.Error(ex.StackTrace, ex);
            }
        }
        public virtual void OutJobPool(MessageEx msg)
        {
            try
            {
                this.OutputJobBuffer.Enqueue(msg);
            }
            catch (Exception ex)
            {
                Tracer.Error(ex.StackTrace, ex);
            }
        }
        public virtual MessageEx GetJob()
        {
            MessageEx msg = null;
            try
            {
                msg = this.InputJobBuffer.Dequeue();
            }
            catch (Exception ex)
            {
                Tracer.Error(ex.StackTrace, ex);
            }
            return msg;
        }
        public virtual bool CheckNewJob()
        {
            bool hasjobs = false;
            try
            {
                hasjobs = InputJobBuffer.Count > 0 || ViewModel.TmpJobQ.Count > 0;
            }
            catch (Exception ex)
            {
                Tracer.Error(ex.StackTrace, ex);
            }
            return hasjobs;
        }
        /// <summary>
        /// The initialize hardware.
        /// </summary>
        public string InitializeHardware()
        {
            
            this.hardwareInitialized = true;
            return HostAddress;
        }

        /// <summary>
        /// The insert card.
        /// </summary>
        /// <param name="validCard">
        /// The valid card.
        /// </param>
        /// <returns>
        /// The WFMachine
        /// </returns>
        public virtual void MachineReady(bool IsReady)
        {
            try
            {
                if (this.WFHost == null)
                {
                    Exception ex = new InvalidOperationException(
                        "The WFMachine StateMachine is null, the workflow may have completed or aborted");
                    Tracer.Error(ex.StackTrace, ex);
                    return;
                }
                var result = new ResultMessage
                {
                    MachineStatus = IsReady ? MachineStatus.Ready : MachineStatus.Running,
                    Message = null,
                    Event = MachineEvent.MachineJoined
                };
                InvokeBookMark(result);
            }
            catch (Exception ex)
            {
                Tracer.Error(ex.StackTrace, ex);
            }

        }
        /// <summary>
        /// The insert card.
        /// </summary>
        /// <param name="validCard">
        /// The valid card.
        /// </param>
        /// <returns>
        /// The WFMachine
        /// </returns>
        public virtual void ExecuteAssignNewJob(MessageEx msg, bool IsCheckDataQuality)
        {
            if (this.WFHost == null)
            {
                Exception ex = new InvalidOperationException(
                                 "The WFMachine StateMachine is null, the workflow may have completed or aborted");
                Tracer.Error(ex.StackTrace, ex);
                return;
            }
            if (this.CurrentState != MachineState.Working)
            {
                this.InputJobPool(msg);
                return;
            }
            var result = new ResultMessage
            {
                MachineStatus = MachineStatus.Unknown,
                Message = msg,
                Event = MachineEvent.NewJobArrived
            };
            InvokeBookMark(result);
        }
        private void InvokeBookMark(BaseEvent result)
        {
            try
            {
                this.ViewModel.Notify(result);
                this.workflowBusy.Reset();
                string bookmark = result.Event.ToString();
                ReadOnlyCollection<BookmarkInfo> bookmarks = WFHost.GetBookmarks();
                var query = from BookmarkInfo bm in bookmarks.AsEnumerable()
                            where bm.BookmarkName == bookmark
                            select bm;
                if (query.Count() == 0)
                {
                    Tracer.Warn(this.HostAddress, bookmark + " can't find");
                    return;
                }
                WFHost.ResumeBookmark(bookmark, result);
            }
            catch (Exception ex)
            {
                Tracer.Error(ex.StackTrace, ex);
            }
        }
        /// <summary>
        /// The insert card.
        /// </summary>
        /// <param name="validCard">
        /// The valid card.
        /// </param>
        /// <returns>
        /// The WFMachine
        /// </returns>
        public virtual void ExecuteQualityCheck(bool checkresult)
        {
            if (this.WFHost == null)
            {
                Exception ex = new InvalidOperationException(
                       "The WFMachine StateMachine is null, the workflow may have completed or aborted");
                Tracer.Error(ex.StackTrace, ex);
                return;
            }
            var result = new ResultMessage
            {
                MachineStatus = MachineStatus.Unknown,
                Message = null,
                Data = checkresult,
                Event = MachineEvent.QualityCheck
            };
            InvokeBookMark(result);
        }
        /// <summary>
        /// The power off.
        /// </summary>
        /// <returns>
        /// A task that will power off the ATM async
        /// </returns>
        public virtual Task TurnPowerOff()
        {
            this.DisableAllCommands();

            if (this.PowerOn && this.WFHost != null)
            {
                this.workflowBusy.Reset();
                return
                    Task.Factory.FromAsync(this.WFHost.BeginCancel, this.WFHost.EndCancel, null).
                        ContinueWith(t => this.WaitForWorkflow());
            }

            // Return a null task if there is nothing to do
            return Task.Factory.StartNew(() => { });
        }

        /// <summary>
        /// The power on.
        /// </summary>
        /// <returns>
        /// The WFMachine
        /// </returns> 
        public virtual void TurnPowerOn(bool flagStartQueryUI)
        {
            try
            {
                var settings = new WFSettings
                {
                    Timeout = this.AtmTimeout,
                    StartQueryUI = flagStartQueryUI,
                    Address = WFMAddress
                };
                var input = new Dictionary<string, object> { { "Settings", settings } };
                this.WFHost = new WorkflowApplication(WFActivity, input)
                {
                    Idle = this.OnWorkflowIdle,
                    Completed = this.OnWorkflowCompleted
                };  
                //WFHost.Extensions.Add(Tracker);  
                this.WFHost.Extensions.Add(this);
                this.WFHost.Extensions.Add(this.ViewModel);

                this.PowerOn = true;
                this.workflowBusy.Reset();
                this.WFHost.Run();
            }
            catch (Exception ex)
            {
                Tracer.Error(ex.StackTrace, ex);
            }
        }


        /// <summary>
        /// The wait for state.
        /// </summary>
        /// <param name="targetState">
        /// The target state.
        /// </param>
        /// <exception cref="TimeoutException">
        /// </exception>
        public void WaitForState(string targetState)
        {
            var start = DateTime.Now;
            while (this.CurrentState != targetState)
            {
                this.WaitForWorkflow();
                if (DateTime.Now.Subtract(start) > this.Timeout)
                {
                    throw new TimeoutException(string.Format("Timeout waiting for state {0}", targetState));
                }
            }
        }

        /// <summary>
        /// The wait for idle.
        /// </summary>
        /// <exception cref="TimeoutException">
        /// </exception>
        public void WaitForWorkflow()
        {
            if (!this.workflowBusy.WaitOne(this.Timeout))
            {
                throw new TimeoutException(string.Format("ATM Timeout waiting for the workflow to idle or complete"));
            }
        }

        #endregion

        #region===========METHOD======================
 
        /// <summary>
        /// The get argument.
        /// </summary>
        /// <param name="record">
        /// The activity record.
        /// </param>
        /// <param name="name">
        /// The argument name.
        /// </param>
        /// <typeparam name="T">
        /// The data type of the argument
        /// </typeparam>
        /// <returns>
        /// The argument value
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// The activity record is null
        /// </exception>
        protected void Track(TrackingRecord record, TimeSpan timeoutValue)
        {
            return;

            if (record is StateMachineStateRecord)
            {
                this.CurrentState = ((StateMachineStateRecord)record).StateName;
            }

            try
            {
                if (record is ActivityStateRecord)
                {
                    ProcessActivityStateRecord(record as ActivityStateRecord);
                }
                else if (record is StateMachineStateRecord)
                {
                    this.ProcessStateMachineTrackingRecord(record as StateMachineStateRecord);
                }
                else if (record is BookmarkResumptionRecord)
                {
                    this.ProcessBookmarkResumptionRecord(record as BookmarkResumptionRecord);
                }
                else if (record is WorkflowInstanceRecord)
                {
                    this.ProcessWorkflowInstanceRecord(record as WorkflowInstanceRecord);
                }
            }
            catch (Exception ex)
            {
                Tracer.Error(ex.StackTrace, ex);
            }
        }

        /// <summary>
        /// The get argument.
        /// </summary>
        /// <param name="record">
        /// The activity record.
        /// </param>
        /// <param name="name">
        /// The argument name.
        /// </param>
        /// <typeparam name="T">
        /// The data type of the argument
        /// </typeparam>
        /// <returns>
        /// The argument value
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// The activity record is null
        /// </exception>
        internal static T GetArgument<T>(ActivityStateRecord record, string name)
        {
            if (record == null)
            {
                throw new ArgumentNullException("record");
            }

            object value;

            if (record.Arguments.TryGetValue(name, out value))
            {
                return (T)value;
            }

            return default(T);
        }

        /// <summary>
        /// Converts a string into an MachineTransition
        /// </summary>
        /// <param name="commandString">
        /// The transition string.
        /// </param>
        /// <returns>
        /// The MachineTransition
        /// </returns>
        /// <exception cref="ArgumentException">
        /// The string cannot be converted to an MachineTransition value
        /// </exception>


        /// <summary>
        /// Disables all commands
        /// </summary>
        private void DisableAllCommands()
        {
            foreach (var cmd in Enum.GetValues(typeof(MachineTransition)).Cast<MachineTransition>())
            {
                this.transitionEnabled[cmd] = false;
            }
        }

        /// <summary>
        /// The enable transition.
        /// </summary>
        /// <param name="transition">
        /// The MachineTransition value
        /// </param>
        private void EnableTransition(MachineTransition transition)
        {
            Tracer.Verbose("WFMachine", "Enable transition " + transition.ToString());
            this.transitionEnabled[transition] = true;
            this.ViewModel.EnableTransition(transition);
        }

        /// <summary>
        /// The initialize commands.
        /// </summary>
        private void InitializeCommands()
        {
            foreach (var cmd in Enum.GetValues(typeof(MachineTransition)).Cast<MachineTransition>())
            {
                this.transitionEnabled.Add(cmd, false);
            }
        }

        /// <summary>
        /// The on workflow completed event handler.
        /// </summary>
        /// <param name="obj">
        /// The event args.
        /// </param>
        private void OnWorkflowCompleted(WorkflowApplicationCompletedEventArgs obj)
        {
            this.WFHost = null;
            this.PowerOn = false;
            this.CurrentState = null;
            this.ViewModel.Clear();
            this.workflowBusy.Set();
        }

        /// <summary>
        /// Handles the workflow idle event
        /// </summary>
        /// <param name="args">
        /// The workflow idle event args.
        /// </param>
        private void OnWorkflowIdle(WorkflowApplicationIdleEventArgs args)
        {

            // Enable all commands with matching bookmarks
            foreach (var cmd in args.Bookmarks.Select(bookmarkInfo => ConvertToAtmTransition(bookmarkInfo.BookmarkName))
                )
            {
                this.EnableTransition(cmd);
            }
            while (MQ.Count > 0)
            {
                MessageEx msg = MQ.Dequeue();
                ViewModel.SendMessage(msg);
            }
            this.workflowBusy.Set();
        }
        private void ResumeCommand(MachineTransition cmd, object data)
        {
            this.workflowBusy.Reset();

            var result = this.WFHost.ResumeBookmark(cmd.ToString(), new AtmTransitionResult { Data = data });
            switch (result)
            {
                case BookmarkResumptionResult.NotFound:
                    throw new InvalidOperationException(
                        string.Format(
                            "The StateMachine is not waiting for the transition {0} right now.  Make sure you added a transition with the correct WaitForTransition value",
                            cmd));
                case BookmarkResumptionResult.NotReady:
                    throw new InvalidOperationException(
                        string.Format("The StateMachine is not ready to handle the transition {0}", cmd));
            }
        }

        /// <summary>
        /// Processes the bookmark resumption record to notify the view model of a transition.
        /// </summary>
        /// <param name="bookmarkResumptionRecord">
        /// The bookmark resumption record.
        /// </param>
        private void ProcessBookmarkResumptionRecord(BookmarkResumptionRecord bookmarkResumptionRecord)
        {
            if (bookmarkResumptionRecord != null && !string.IsNullOrWhiteSpace(bookmarkResumptionRecord.BookmarkName))
            {
                this.ViewModel.Notify(
                    ConvertToAtmTransition(bookmarkResumptionRecord.BookmarkName), bookmarkResumptionRecord.Payload);
            }
        }

        /// <summary>
        /// Processes State Machine Tracking records to notify the view model of a state change
        /// </summary>
        /// <param name="stateRecord">
        /// The state Record.
        /// </param>
        private void ProcessStateMachineTrackingRecord(StateMachineStateRecord stateRecord)
        {
            if (stateRecord == null)
            {
                return;
            }

            // All commands are disabled when the state changes
            // They will be enabled when the state machine schedules them
            //this.DisableAllCommands();

            this.CurrentState = stateRecord.StateName;
            this.ViewModel.StateChanged(stateRecord.StateName);
        }

        /// <summary>
        /// Processes WorkflowInstanceRecords to notify the view model of PowerOn/Off events
        /// </summary>
        /// <param name="workflowInstanceRecord">
        /// The workflow instance record.
        /// </param>
        private void ProcessWorkflowInstanceRecord(WorkflowInstanceRecord workflowInstanceRecord)
        {
            switch (workflowInstanceRecord.State)
            {
                case "Started":
                    this.ViewModel.Notify(MachineTransition.PowerOn, null);
                    break;
                case "Completed":
                    this.ViewModel.Notify(MachineTransition.PowerOff, null);
                    break;
                case "Canceled":
                    this.ViewModel.Notify(MachineTransition.PowerOff, null);
                    break;
            }
        }
        /// <summary>
        /// The process activity state record.
        /// </summary>
        /// <param name="activityStateRecord">
        /// The activity state record.
        /// </param>
        private static void ProcessActivityStateRecord(ActivityStateRecord activityStateRecord)
        {
            if (activityStateRecord == null)
            {
                return;
            }

            if (activityStateRecord.Activity.Name.ToLower() == "wait for timeout")
            {
                Tracer.Info("WFActivity", "You have " + GetArgument<TimeSpan>(activityStateRecord, "Duration").TotalSeconds.ToString()
                    + "seconds to enter a command");
            }
        }
        #endregion
    }
}